/**
 * Copyright (C) 2008 aileron.cc
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.aileron.junit.runner.guice;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;

/**
 * junit-test-runner
 * 
 * @author Aileron
 * 
 */
public class GuiceInjectRunner extends JUnit4ClassRunner
{
    /**
     * injector の取得
     * 
     * @param targetClass
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ModuleConfigurationException
     */
    private Injector getInjector(final Class<?> targetClass)
            throws InstantiationException, IllegalAccessException, Exception
    {
        if (GuiceInjectRunnerInterface.class.isAssignableFrom(targetClass))
        {
            final Module module = targetClass.asSubclass(
                    GuiceInjectRunnerInterface.class).newInstance().getModule();
            return Guice.createInjector(module);
        }
        final GuiceInjectRunnerParameterMultiple multipleAnnotation = targetClass
            .getAnnotation(GuiceInjectRunnerParameterMultiple.class);
        final GuiceInjectRunnerParameterArguments argumentsAnnotation = targetClass
            .getAnnotation(GuiceInjectRunnerParameterArguments.class);
        
        if(argumentsAnnotation!=null)
        {
            return getArgumentsInjector(argumentsAnnotation);
        }

        if(multipleAnnotation!=null)
        {
            return getMultipleInjector(multipleAnnotation);
        }
        
        throw new NullPointerException("GuiceInjectRunnerParameter is null");
    }
    
    /**
     * 
     * @param multipleAnnotation
     * @return
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    private Injector getMultipleInjector(
            final GuiceInjectRunnerParameterMultiple multipleAnnotation) throws InstantiationException, IllegalAccessException
    {
        final ArrayList<Module> modules = new ArrayList<Module>();
        for(Class<? extends Module> moduleClass : multipleAnnotation.value())
        {
            modules.add(moduleClass.newInstance());
        }
        return Guice.createInjector(modules);
    }
    
    /**
     * 
     * @param argumentsAnnotation
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws SecurityException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    private Injector getArgumentsInjector(
            final GuiceInjectRunnerParameterArguments argumentsAnnotation) throws InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException
    {
        final Class<? extends Module> moduleClass = argumentsAnnotation.value();
        final Module module;
        if (argumentsAnnotation.arguments().length == 0)
        {
            module = moduleClass.newInstance();
        }
        else
        {
            module = moduleClass.getConstructor(String[].class).newInstance(
                    (Object[]) argumentsAnnotation.arguments());
        }
        return Guice.createInjector(module);        
    }

    /*
     * (非 Javadoc)
     * @see org.junit.internal.runners.JUnit4ClassRunner#createTest()
     */
    @Override
    protected Object createTest() throws Exception
    {
        final Object object = getTestClass().getConstructor().newInstance();
        injector.injectMembers(object);
        return object;
    }

    /**
     * constractor
     * 
     * @param targetClass
     * @throws InitializationError
     * @throws InitializationError
     */
    public GuiceInjectRunner(final Class<?> targetClass)
            throws InitializationError
    {
        super(targetClass);
        try
        {
            this.injector = getInjector(targetClass);
        }
        catch (final Throwable e)
        {
            e.printStackTrace();
            throw new Error(e);
        }
    }

    /**
     * injector
     */
    private final Injector injector;
}
